﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Net.Sockets;
using System.Net;
using System.Collections;
using AOChat;
using ScottGarland;

namespace AOChat
{
    public class AOChat
    {
        private string serverseed = null;

        public string ServerSeed
        {
            get { return serverseed; }
            set { serverseed = value; }
        }
        //public AOChat(object cb) : this(cb, null)
        //{
        //}
        public AOChat()
        {
            this.disconnect();
            this.login_num = 0;
        }
        public AOChat(object args)
        {
            //DoTests();
            //this.callback = cb;
            this.cbargs = args;
            this.disconnect();
            this.login_num = 0;
        }

        private void disconnect()
        {
            if (theSocket== null)
            {
                socket_close();
            }
            this.theSocket = null;
            this.serverseed = null;
            this.chars = null;
            this.last_packet = 0;
            this.last_ping = DateTime.MinValue;
            this.state = "disconnected";
            this.id = null;
            this.gid = new Dictionary<string, int>();//array();
            this.grp = new Dictionary<string, int>();// array();
            this.chars = new Dictionary<string, string>();
            this.buddies = new Dictionary<string, string>();

        }
        public static string CHAT_SERVER = "proddm01.ams.ageofconan.com";
        protected object connect()
        {
             
            return connect(CHAT_SERVER, serverPort);
        }

        protected object connect(string server, int port)
        {
            if (this.state == "connect")
                throw new Exception("Already connected.");
            
            Socket s = socket_create();

            if (s == null) throw new Exception("Cannot create socket.");
            
            this.theSocket = s;
            this.state = "auth";
            if (socket_connect(s, server, port) == false)
            {
                Console.WriteLine("COULD NOT CONNECT TO THE AOC CHAT SERVER AT " + server + ":" + port);
                this.disconnect();
                AOChatPacket packet = get_packet();
                if (packet == null || packet.type != AOCP_LOGIN_SEED)
                {
                    this.disconnect();
                    Console.WriteLine("Received invalid greeting packet from AOC Chat server.");
                    return false; //
                }
            }
            return s;
        }

        AOChatPacket wait_for_packet()
        {
            return wait_for_packet(1);
        }
        AOChatPacket wait_for_packet(int time)
        {
            object b = array();
            object c = array();
            int sec = (int)time;
            int usec;
            //if (time))
            //{
            //    usec = (int)(time * 1000000 % 1000000);
            //}
            //i
            //{
            //    usec = 0;
            //}
            object a = array(this.theSocket);
            if (!socket_select(a, b, c, sec))
            {
                DateTime nowTime = DateTime.Now;
                if ( (nowTime - (DateTime)(this.last_packet)).TotalSeconds > 60)
                {
                    if (((TimeSpan)(nowTime - (DateTime)this.last_ping)).TotalSeconds > 60)
                    {
                        this.send_ping();
                    }
                }
                return null;
            }
            return this.get_packet();
        }

        byte[] read_data(int len)
        {
            byte[] data = null;
            int rlen = len;
            while (rlen > 0)
            {
                byte[] socketData;
                socketData = socket_read(this.theSocket, rlen);
                //if (tmp.GetType() == typeof(bool) && (bool)tmp == false)
                if (socketData ==null)
                {
                    if (this.theSocket == null)
                    {
                        this.disconnect();
                        Debug.WriteLine("Read error...");
                        die("Read error");
                        return null;
                    }
                    else
                    {
                        Debug.WriteLine("Read error line 268 AocChat.php");
                        
                        throw new Exception("Read error: " + this.theSocket.ToString() );
                    }
                }

                if(socketData.Length == 0)
			    {
				    echo("Read error: EOF\n");

                    if (this.theSocket == null)
                    {
                        this.disconnect();
                        die("Read error: Too many EOF errors, disconnecting.\n");
                    }
                    else
                        return null;
			    }

			    data = socketData;
			    rlen -= socketData.Length;
           }
            return data;
        }
        private AOChatPacket get_packet()
        {
            byte[] head = this.read_data(4);
            if (head == null || head.Length != 4)
            {
                return null; //"disconnected";
            }

            List<ushort> list0 =    unpackn2( head);
            int type = (int)list0[0], len = (int) list0[1];
            byte[] data = this.read_data(len);
            //if(is_resource(this.debug))
            //{
            //    fwrite(this.debug, "<<<<<\n");
            //    fwrite(this.debug, head);
            //    fwrite(this.debug, data);
            //    fwrite(this.debug, "\n=====\n");
            //}
            Console.WriteLine("Received: ");
            if (head == null)
                Console.WriteLine("Head: NULL!");
            else
                Console.WriteLine("Head:" + ASCIIEncoding.ASCII.GetString(head));

            if (data == null)
                Console.WriteLine("Data: NULL!");
            else 
                Console.WriteLine("Data:" + ASCIIEncoding.ASCII.GetString(data));
            Console.WriteLine("--------");

            if (data == null || data.Length == 0)
            {
                Console.WriteLine("Warning: Empty packet was recieved from the server..");
            }
            AOChatPacket packet = new AOChatPacket("in", type, data);
            object[] list;
            int id;
            string name;

            switch ((int)type)
            {
                case AOCP_LOGIN_SEED:
                    if (packet.args.Length != 0)
                    {
                        this.ServerSeed = packet.args[0].ToString();
                    }
                    else { this.ServerSeed = null; }
                    break;

                case AOCP_CLIENT_NAME:
                    id = (int)packet.args[0];
                    list = packet.args.Cast<string>().ToArray();
                    string unknown = (string)list[1];
                    name = (string)list[2];

                    id_dict = new Dictionary<string, int>();
                    name = ucfirst(strtolower(name));
                    //this.id_dict.Add(id, (string)name);
                    this.id_dict.Add((string)name, id);
                    break;

                case AOCP_CLIENT_LOOKUP:
                    //list(id, name) = packet.args;
                    list = packet.args.Cast<object>().ToArray();
                    id = (int)list[0];
                    name = (string)list[1];

                    name = ucfirst(strtolower(name));
                    //this.id[id]   = name;
                    //this.id[name] = id;
                    //this.id_dict.Add(id, (string)name);
                    this.id_dict.Add((string)name, id);
                    break;

                case AOCP_GROUP_ANNOUNCE:
                    //list(gid, name, status) = packet.args;
                    list = packet.args;//.Cast<string>().ToArray();;
                    int gid = (int)list[0];
                    name = (string)list[1];
                    string status = (string)list[2];

                    //this.grp[gid] = status;
                    this.grp.Add(status, gid);

                    //this.gid[gid] = name;
                    this.gid.Add(name, gid);

                    //this.gid[strtolower(name)] = gid;
                    this.gid.Add(strtolower(name), gid);
                    break;

                //case AOCP_GROUP_MESSAGE:
                //    /* Hack to support extended messages */
                //    if (packet.args[1] is int && (int)packet.args[1] == 0 && packet.args[2].ToString().Substring(0, 2) == "~&")
                //    {
                //        AOExtMsg em = new AOExtMsg((string)packet.args[2]);
                //        if (em.type != AOEM_UNKNOWN)
                //        {
                //            packet.args[2] = em.text;
                //            Debug.WriteLine("Cannot translate : packet.args[] = em;");
                //            throw new Exception("Cannot translate : packet.args[] = em;");
                //        }
                //    }
                //    break;

                //case AOCP_BUDDY_ADD:

                //    //list(bid, bonline, blevel, blocation, bclass) = packet.args;
                //    list = packet.args.Cast<string>().ToArray();
                //    int bid = (int) list[0];
                //    bool bonline = (bool)list[1];
                //    string blevel = (string)list[2];
                //    string blocation = (string)list[3];
                //    string bclass = (string)list[4];


                //    //this.buddies[bid] = (bonline    ? AOC_BUDDY_ONLINE : 0)|
                //    //                                  (1 ? AOC_BUDDY_KNOWN  : 0);
                //    this.buddies.Add(bid, (bonline ?
                //        AOC_BUDDY_ONLINE : 0) | AOC_BUDDY_KNOWN);
                //    break;

                //case AOCP_BUDDY_REMOVE:
                //    if (this.buddies.ContainsKey((string)packet.args[0]))
                //        this.buddies.Remove((string)packet.args[0]);
                //    break;

            }


       this.last_packet = DateTime.Now;

        //if(function_exists(this.callback))
        //{
        //    call_user_func(this.callback, packet.type, packet.args, this.cbargs);
        //}

        //return packet;


       //dont do callbacks - jp
       //if (this.callback != null)
       //{

       //    call_user_func(this.callback, packet.type, packet.args, this.cbargs);
       //}

       return packet;
}













        
    ///*
    //Authentication function
    //*/
    Dictionary<string, string> authenticate(string username, string password)
    {

    //function authenticate(username, password)
    //{
    //    // If we are not authenticating, bail
        if(this.state != "auth")
        {
            die("AOChat: expecting authentication not \"" + this.state + "\"\n");
        }

        byte[] key = generate_login_key(this.serverseed, username, password);

    //    // Prepare and send the login packet.
        int keyLen = key.Length;

        AOChatPacket pak = new AOChatPacket("out", AOCP_LOGIN_REQUEST, new object[] { 0, ASCIIEncoding.ASCII.GetBytes(username), key });
        this.send_packet(pak);

        //get a packet immediately back from the server.
        AOChatPacket packetFromServer = this.get_packet();

    //    // If we recieve anything but the character list, something's wrong.
        if(packetFromServer == null)
        {
            Debug.WriteLine("Warning: Packet recieved from server is null.");
            return null;
        }
        else if (packetFromServer.type != AOCP_LOGIN_CHARLIST)
        {
            Debug.WriteLine("Warning: expected packet type to be AOCP_LOGIN_CHARLIST.");
            Debug.WriteLine("Attempt to login will fail.");
            throw new Exception("Auntheticate error: Expected packet type to be AOCP_LOGIN_CHARLIST.");
        }

        object[] lroot = packetFromServer.args;//(List<object>)packetFromServer.args;
        // Prepare an array of all characters returned
        for(int i=0; i< lroot.Length; i++)
        {
            this.chars = new Dictionary<string, string>();
            

            //this.chars = //array(
            //"id"     , ((List<object>)lroot[0])[i],
            this.chars.Add("id", ((List<object>)(lroot[0]))[i].ToString());

            //"name"   , ucfirst(strtolower((List<object>)lroot[1])[i].ToString()),
            this.chars.Add("name", ucfirst(strtolower(((List<object>)(lroot[1]))[i].ToString())));
            //"level"  , ((List<object>)lroot[2][i],\
            this.chars.Add("level", ((List<object>) lroot[2])[i].ToString());
            //"online" ,  ((List<object>)lroot[3][i]);
            this.chars.Add("online", ((List<object>)lroot[3])[i].ToString());
        }

        this.username = username;

    //    // Authentication successfull, we are now logging in
        this.state    = "login";

        return this.chars;
     }

        //function send_packet(packet)
        bool send_packet(AOChatPacket packet)
        {
            //    data = pack("n2", packet.type, strlen(packet.data)) . packet.data;
            if (packet.data is byte[])
            {
                byte[] oldData = (byte[])packet.data;

                //byte[] pack = packn2(packet.type, oldData.Length);
                //byte[] data = Append(pack, oldData);
                //    if(is_resource(this.debug))
                //    {
                //        fwrite(this.debug, ">>>>>\n");
                //        fwrite(this.debug, data);
                //        fwrite(this.debug, "\n=====\n");
                //    }
                socket_write(this.theSocket, oldData, oldData.Length);
                return true;
            }
            //else if (packet.data is object[])
            //{
            //    //object[] doa = (object[])packet.data;

            //    //if (doa[0] is int)
            //    //{
            //    //    int i = (int)doa[0];
            //    //    string s1 = (string)doa[1];
            //    //    string s2 = (string)doa[2];
            //    //    byte[] a1 = StrToAscii(s1);
            //    //    byte[] a2 = StrToAscii(s2);
            //    //    throw new Exception("Cannot send pack of type object[]{int, ...} " );
            //    //}
            //    if (doa[1] is string)
            //    {
            //        byte[] a1 = (byte[])doa[0];
            //        byte[] a2 = StrToAscii((string)doa[1]);
            //        byte[] a_to_send = Append(a1, a2);
            //        byte[] pack = packn2(packet.type, a_to_send.Length);
            //        byte[] data = Append(pack, a_to_send);
            //        socket_write(this.theSocket, data, data.Length);
            //        return true;
            //    } else
            //        throw new Exception("Cannot send pack of type object[]{byte[], ...}");

            //}
            else
            {
                throw new Exception("Cannot send pack of type " + packet.data.GetType().ToString());
            }
            return true;
        }
        //change this to your own
        public static string USER_NAME = "stupid_user1";
        public static string PASSWORD = "stupid_pw123";
        public static string CHARACTER = "Johndoe";
        public static int Main()
        {
            AOChat chatObject = new AOChat();
            chatObject.DoTests();

            Console.Out.WriteLine("Connecting to chat server...");
            chatObject.connect();
            Console.WriteLine("Connected.");

            Console.WriteLine("Aunthenticating...");
            chatObject.authenticate(AOChat.USER_NAME, AOChat.PASSWORD);
            Console.WriteLine("Logging in...");
            chatObject.login(CHARACTER);

            //Console.Out.WriteLine("Disconnecting from chat server...");
            //chatObject.disconnect();
            //Console.Out.WriteLine("Disconnected.");

            Console.Out.WriteLine("Press enter to continue...");
            Console.In.ReadLine();

            return 0;
        }
        /// <summary>
        /// Throws an exception if it fails any preliminary tests.
        /// 
        /// </summary>
        /// <returns></returns>
        private bool DoTests()
        {
            Console.WriteLine("*ScottGarland.BigInteger test...");
            BigInteger bi1 = new BigInteger("39986");
            string bi1_val = bi1.ToString(10);
            if (bi1_val != "39986")
                throw new Exception("Failed BigInteger ToString() test.");

            Console.WriteLine("*Bighexdec test (a)");
            BigInteger dhY = phpMath.bighexdec("9c32");
            string dhY_val = dhY.ToString(10);
            if (dhY_val != "39986")
                throw new Exception("*Failed bighexdec test(a)! bighexdec returned: " + dhY.ToString() + " but should be equal to " + "39986");
            Console.WriteLine("*Bighexdec test (a) Success.");

            Console.WriteLine("*Bighexdec test (b)");
            BigInteger dhN = phpMath.bighexdec("0eca2");
            if (dhN.ToString(10) != "60578")
                throw new Exception("Failed bighexdec test(b)!");

            Console.WriteLine("*Pack/Unpack...");
            byte[] ba = packn2(2, 55);
            List<ushort> luis = unpackn2(ba);
            if (luis[0] != 2 && luis[1] != 55)
                throw new Exception("Failed packn2/unpackn2 test.");
            Console.WriteLine("*Pack/Unpack Success!");

            byte[] ba2 = packn(55);
            ushort[] res2 = unpackn(ba2);
            if (res2[0] != 55)
                throw new Exception("Failed packn/unpackn test.");

            Console.WriteLine("*powmod test");
            BigInteger bi = phpMath.powmod(new BigInteger(39986), new BigInteger(2), new BigInteger(3));
            if (bi.ToString() != "1")
                throw new Exception("Powmod function failed.");
            Console.WriteLine("*powmod SUCCESS");

            Console.WriteLine("Test 7: packN test..");
            byte[] strlen = packN(UInt32.MaxValue);
            Console.WriteLine("packN returned " + ASCIIEncoding.ASCII.GetString(strlen));
            uint uarr = unpackN(strlen);
            if (uarr != UInt32.MaxValue)
                throw new Exception("packN test failed.");

            Console.WriteLine("*Login Key generation...");
            byte[] key = null;
            for (int i = 1; i <= 1; i++)
            {
                key = generate_login_key(this.serverseed, AOChat.USER_NAME, AOChat.PASSWORD);
                //Console.WriteLine("Login key=" + ASCIIEncoding.ASCII.GetString(key));
                Console.WriteLine("Login key length = " + key.Length);

                Console.WriteLine("*AOC Chat Pack creation (out packet)...");
                AOChatPacket pak = new AOChatPacket("out", AOCP_LOGIN_REQUEST, new object[] { 0, ASCIIEncoding.ASCII.GetBytes(USER_NAME), key });
                Console.WriteLine("Packet Info: ");
                Console.WriteLine( "Length:" + pak.data.Length);
            }
            Console.WriteLine("*Success!");



            return true;
        }

        private uint unpackN(byte[] strlen)
        {
            Array.Reverse(strlen);
            return BitConverter.ToUInt32(strlen, 0);
        }
        public static byte[] packn(int p)
        {
            //return (uint)p + p_2;
            byte[] ba = BitConverter.GetBytes((ushort)p);
            Array.Reverse(ba);
            return ba;
        }
        internal static byte[] packN(Int32 p)
        {
            return packN((UInt32)p);
        }
        internal static byte[] packN(UInt32 p)
        {
            byte[] ba = BitConverter.GetBytes((UInt32)p);
            Array.Reverse(ba);
            return ba;
        }

        public static byte[] packn2(int p, int p_2)
        {
            byte[] p1 = packn(p);
            byte[] p2 = packn(p_2);
            byte[] pboth = Append(p1, p2);
            return pboth;
        }
        public static List<ushort> unpackn2(byte[] head)
        {
            if (head.Length == 0) return null;
            ushort[] ua = unpackn(head);
            List<ushort> lst = new List<ushort>();
            lst.Add(ua[1]); //reverse array result
            lst.Add(ua[0]);
            return lst;
        }

        public static char[] unpackC(byte[] data)
        {
            char[] unpackeda = new char[data.Length];
            int idx = 0;
            foreach (byte b in data)
            {
                unpackeda[idx++] = (char)b;
            }
            return unpackeda;
        }
        //n	unsigned short (always 16 bit, big endian byte order) //see http://php.net/manual/en/function.unpack.php
        public static ushort[] unpackn(byte[] ba)
        {
            Array.Reverse(ba);
            int sz = ba.Length / sizeof(ushort);
            ushort[] unpacked = new ushort[sz];
            int unpack_idx = 0;
            for (int idx = 0; idx < ba.Length; idx+= sizeof(ushort))
            {
                unpacked[unpack_idx] = BitConverter.ToUInt16(ba, idx );
                unpack_idx++;
            }
            return unpacked;
        }

  


        public static byte[] Append(byte[] p1, byte[] p2)
        {
            if (p1 == null && p2 == null)
                return null;
            if (p1 == null)
                return p2;
            if (p2 == null)
                return p1;

            byte[] pboth = new byte[p1.Length + p2.Length ];
            if (p1!=null)
            for (int i = 0; i < p1.Length; i++)
            {
                pboth[i] = p1[i];
            }
            if (p2 !=null)
            for (int i2 = 0; i2 < p2.Length; i2++)
            {
                pboth[i2 + p1.Length] = p2[i2];
            }
            return pboth;
        }

    ///*
    //Login Function

    //This function gets called after we have successfully authenticated and are ready to select a character to login.
    //*/
    //function login(char)
    bool login(object character_)
    {
    //    // If we have not authenticated, bail
        if(this.state != "login")
        {
            if (this.login_num >= 1)
            {
                die("AOChat: authentication failed. Keygeneration failure likely\n");
            }
            else
            {
                die("AOChat: not expecting login.\n");
            }
        }

    //    // Allows us to catch if we've been here before and failed.
        this.login_num += 1;
    //    // Check if we have been given a character id or character name
        string field = null;
        if(is_int(character_))
        {
            field = "id";
        }
        else if(is_string(character_))
        {
            field = "name";
            character_  = ucfirst(strtolower(character_.ToString()));
        }

    //    // Make sure we have a valid character to login

        //PURPOSELY COMMENTED OUT, CODE IS CONFUSING (ISNOT ARRAY THEN IT ITERATES THROUGH AS IF IT WERE AN ARRAY).

        // Make sure we have a valid character to login
        //if(!is_array($char))
        //{
        //    if(empty($field))
        //    {
        //        return false;
        //    }
        //    else
        //    {
        //        foreach($this->chars as $e)
        //        {
        //            if($e[$field] == $char)
        //            {
        //                $char = $e;
        //                break;
        //            }
        //        }
        //    }
        //}

        if(!is_array(character_))
        {
            die("AOChat: no valid character to login.\n");
        }

        Dictionary<string, string> character_list = (Dictionary<string, string>)character_;
        // Prepare the login packet and send it
        AOChatPacket pq = new AOChatPacket("out", AOCP_LOGIN_SELECT, character_list["id"]);
        this.send_packet(pq);
        AOChatPacket pr = this.get_packet();

        // Check if login was successfull.
        if(pr.type != AOCP_LOGIN_OK)
        {
            return false;
        }
        // We are authenticated and logged in. Everything is ok.
        this.state = "ok";

        return true;
    }

    private bool is_array(object character_)
    {
        return character_ is Dictionary<object, object> || 
            character_ is Dictionary<object, string> || 
            character_ is Dictionary<string, object> || 
            character_ is Dictionary<string, string>;
    }

    private bool empty(string field)
    {
        return field == null || field == "";
    }

    private bool is_string(object character_)
    {
        return character_ is string;
    }

    private bool is_int(object character_)
    {
        return character_.GetType() == typeof(int);
    }

    ///* User and group lookup functions */
    //function lookup_user(u)
    object lookup_user(string u)
    {
        u = ucfirst(strtolower(u));

        if(this.id.ContainsKey(u))
        {
            return this.id[u];
        }

        AOChatPacket pq = new AOChatPacket("out", AOCP_CLIENT_LOOKUP, u);
        this.send_packet(pq);
        AOChatPacket pr = this.get_packet();

        if (pr.type != AOCP_CLIENT_LOOKUP)
        {
            return false;
        }
        this.id[u] = (int)pr.args[0];

        return this.id[u];
    }

    //function get_uid(user)
    int get_uid(object user)
    {
        int uid;

        if (!(user is int))
        {
            uid = (int)this.lookup_user(user.ToString());
        }
        else
            uid = 0;
        
        if(uid == -1 || uid == 0)
        {
            uid = -1;
        }
        return uid;
    }

    //function get_uname(user)
    object get_uname(object user)
    {   if(!(user is int))
        {
            return user;
        }
        else
        {
            return this.lookup_user(user.ToString());
        }
    }

    //string lookup_group(ref string arg)
    //{
    //    bool is_gid = arg.Length == 5 && (ord(arg[0]) & ~0x80) < 0x10;
    //    if(is_gid)
    //    {
    //        return arg;
    //    }

    //    if(!is_gid)
    //    {
    //        arg = strtolower(arg);
    //    }
    //    return isset(this.gid, arg) ? this.gid[arg] : null;
    //}

    private bool isset(Dictionary<string, int> dictionary, string arg)
    {
        return dictionary.ContainsKey(arg);
    }

    private int ord(char p)
    {
        return (int)p;
    }

    //function get_gid(g)
    int get_gid(string g)    
    {
        return isset(this.gid, g) ? this.gid[g] : -1;
    }

    ////function get_gname(g)
    //string get_gname(string g)
    //{
    //    //int gid_2 = get_gid(g);//this.lookup_group(g);
    //    //if(gid_2 == null)
    //    //{
    //    //    return null;
    //    //} else
    //    //    return this.gid[gid_2];
    //}

    ///* Sending various packets */
    //function send_ping()
    bool send_ping()
    {
        this.last_ping = DateTime.Now;
        return this.send_packet(new AOChatPacket("out", AOCP_PING, "AOChat.php"));
    }

    //function send_tell(user, msg, blob = "\0")
    bool send_tell(object user, string msg, string blob)
    {
        int uid = this.get_uid(user);
        return this.send_packet(new AOChatPacket("out", AOCP_MSG_PRIVATE, new object[]{uid, (string) msg,(string) blob}));
    }

    ///* General chat groups */
    //function send_group(group, msg, blob = "\0")
    bool send_group(string group, string msg, string blob)
    {
        object gid = this.get_gid(group);
        if(gid is bool == (bool)gid == false)
        {
            return false;
        }
        return this.send_packet(new AOChatPacket("out", AOCP_GROUP_MESSAGE, new object[]{gid, msg, blob}));
    }

    //private object array(object gid, string msg, string blob)
    //{
    //    return new object[] { gid, msg, blob };
    //}
        
    //function group_join(group)
    bool group_join(string group)
    {   
        //int gid = this.get_gid(group);
        //if(gid is bool && (bool)gid == false)
        //{
        //    return false;
        //}
        int barg1 =  ((int)this.grp[group]) & ~AOC_GROUP_MUTE;
        return this.send_packet(new AOChatPacket("out", AOCP_GROUP_DATA_SET, array(gid, barg1, "\0")));
    }

    private object array(object gid, int barg1, string p)
    {
        return new object[] { gid, barg1, p };
    }

    //function group_leave(group)
    bool group_leave(string group)
    {
        int gid = this.get_gid(group);
        if(gid == -1)
        {
            return false;
        }
        return this.send_packet(
            new AOChatPacket("out", AOCP_GROUP_DATA_SET, array(gid,((int)gid) | AOC_GROUP_MUTE, "\0"))
        );
    }

    ////function group_status(group)
    //string group_status(string group)
    //{
    //    string gid = this.get_gid(group);
    //    return this.grp[gid];
    //}

    //private bool istrue(object gid)
    //{
    //    return gid is bool && (bool)gid == true;
    //}
    //private bool isfalse(object gid)
    //{
    //    return gid is bool && (bool)gid == false;
    //}

    ///* Private chat groups */
    object send_privgroup(string group, string msg)
    {
        return send_privgroup(group, msg, "\0");
    }
    //function send_privgroup(group, msg, blob = "\0")
    object send_privgroup(string group, string msg, string blob)
    {
        int gid = this.get_uid(group);
        if (gid <=0)
        {
            return false;
        }

        return this.send_packet(new AOChatPacket("out", AOCP_PRIVGRP_MESSAGE, new object[]{gid, msg, blob}));
    }

    object privategroup_join(string group)
    {
        int gid = this.get_uid(group);
        if (gid <=0)
        {
            return false;
        }
        return this.send_packet(new AOChatPacket("out", AOCP_PRIVGRP_JOIN, gid));
    }

    object privategroup_leave(string group)
    {
        int gid = this.get_uid(group);
        if (gid <=0)
        {
            return false;
        }
        return this.send_packet(new AOChatPacket("out", AOCP_PRIVGRP_PART, gid));
    }

    object privategroup_invite(object user)
    {
        int uid = this.get_uid(user);
        if( uid <=0 )
        {
            return false;
        }
        return this.send_packet(new AOChatPacket("out", AOCP_PRIVGRP_INVITE, uid));
    }

    //function privategroup_kick(user)
    //{
    //    if((uid = this.get_uid(user)) === false)
    //    {
    //        return false;
    //    }
    //    return this.send_packet(new AOChatPacket("out", AOCP_PRIVGRP_KICK, uid));
    //}

    //function privategroup_kick_all()
    //{
    //    return this.send_packet(new AOChatPacket("out", AOCP_PRIVGRP_KICKALL, 0));
    //}

    ///* Buddies */
    //function buddy_add(user, type="\1")
    //{
    //    if((uid = this.get_uid(user)) === false)
    //    {
    //        return false;
    //    }

    //    if(uid === this.char['id'])
    //    {
    //        return false;
    //    }

    //    return this.send_packet(new AOChatPacket("out", AOCP_BUDDY_ADD, uid));
    //}

    //function buddy_remove(user)
    //{
    //    if((uid = this.get_uid(user)) === false)
    //    {
    //        return false;
    //    }
    //    return this.send_packet(new AOChatPacket("out", AOCP_BUDDY_REMOVE, uid));
    //}

    //function buddy_remove_unknown()
    //{
    //    return this.send_packet(new AOChatPacket("out", AOCP_CC,
    //    array(array(2,"rembuddy", "?"))));
    //}

    //function buddy_exists(who)
    //{
    //    if((uid = this.get_uid(who)) === false)
    //    {
    //        return false;
    //    }
    //    return (int)this.buddies[uid];
    //}

    //function buddy_online(who)
    //{
    //    return (this.buddy_exists(who) & AOC_BUDDY_ONLINE) ? true : false;
    //}



    // C# to convert a string to a byte array.
    public static byte[] StrToAscii(string str)
    {
        byte[] asciiString = ASCIIEncoding.ASCII.GetBytes(str);//new byte[str.Length];
        //for (int i = 0; i < str.Length; i++)
        //{
        //    try
        //    {
        //        asciiString[i] = (byte)str[i];
        //    }
        //    catch (Exception)
        //    {
        //        throw new Exception("Cannot convert this unicode string to bytes...");
        //    }
        //}
        return asciiString;
    }


    private void socket_write(Socket p, byte[] data, int strlen_data)
        {
            //byte[] bdata = StrToByteArray(data);
            p.Send(data);
        }

private string ucfirst(string p)
{
    if (p == null)
        return null;
    else if (p == "")
        return "";
    else if (p.Length == 1)
       p = p[0].ToString().ToUpper();
    else 
        p = p[0].ToString().ToUpper() + p.Substring(1, p.Length - 1);
    return p;
}

private string strtolower(string name)
{
    return name.ToLower();
}

public static void echo(string p)
{
 	Debug.WriteLine(p);
    Console.WriteLine(p);
}

        private void die(string p)
        {
            Debug.WriteLine(p);
            throw new Exception("Program needs to terminate. Reason: " + p);
        }

        private byte[] socket_read(object p, int rlen)
        {
            byte[] b = new byte[rlen];
            this.theSocket.Receive(b, rlen, SocketFlags.None);
            return b;
        }

        private bool socket_select(object a, object b, object c, int sec)
        {
            theSocket.Bind(new IPEndPoint(IPAddress.Any, serverPort));
            theSocket.Listen(10);
            theSocket.Accept();
            return true;
        }

        private object array(object p)
        {
            object[] a = new object[] { p };
            return a;
        }
        
        private bool socket_connect(Socket s, string server, int port)
        {
            s.Connect(server, port);
            return true;
        }

        private Socket socket_create()
        {
            //		$s = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            //this.sockInfo = new SocketInformation();
            return DoSocketGet();
        }


         public static Socket DoSocketGet()
        {
            try
            {


                // Define those variables to be evaluated in the next for loop and 
                // then used to connect to the server. These variables are defined
                // outside the for loop to make them accessible there after.
                Socket s = null;
                IPEndPoint hostEndPoint;
                IPAddress hostAddress = null;

                // Get DNS host information.
                IPHostEntry hostInfo = Dns.GetHostEntry(AOChat.CHAT_SERVER);
                // Get the DNS IP addresses associated with the host.
                IPAddress[] IPaddresses = hostInfo.AddressList;

                // Evaluate the socket and receiving host IPAddress and IPEndPoint. 
                //for (int index = 0; index < IPaddresses.Length; index++)
                int index = 0;
                {
                    hostAddress = IPaddresses[index];
                    hostEndPoint = new IPEndPoint(hostAddress, AOChat.serverPort);


                    // Creates the Socket to send data over a TCP connection.
                    s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                } // End of the for loop.      

                return s;
                
            } // End of the try block.
            catch (Exception e)
            {
                Console.WriteLine("Exception caught!!!");
                Console.WriteLine("Source : " + e.Source);
                Console.WriteLine("Message : " + e.Message);
                return null;
            }
        }

        private object array()
        {
            return new object[] { };
        }

        private void socket_close()
        {
            if (theSocket !=null)
                theSocket.Close();
        }
        
        //set_time_limit(0;
        //ini_set("html_errors", 0;

        /* AOChatPacket type definitions - so we won't have to use the number IDs
        * .. I did not distinct between server and client message types, as
        * they are mostly the same for same type packets, but maybe it should
        * have been done anyway..  // auno - 2004/mar/26
        */
        public const int AOCP_LOGIN_SEED=		0;
        public const int AOCP_LOGIN_REQUEST=		2;
        public const int AOCP_LOGIN_SELECT=		3;
        public const int AOCP_LOGIN_OK=			5;
        public const int AOCP_LOGIN_ERROR=		6;
        public const int AOCP_LOGIN_CHARLIST=		7;
        public const int AOCP_CLIENT_UNKNOWN=		10;
        public const int AOCP_CLIENT_NAME=		20;
        public const int AOCP_CLIENT_LOOKUP=		21;
        public const int AOCP_MSG_PRIVATE=		30;
        public const int AOCP_MSG_VICINITY=		34;
        public const int AOCP_MSG_VICINITYA=		35;
        public const int AOCP_MSG_SYSTEM=		36;
        public const int AOCP_CHAT_NOTICE=		37;
        public const int AOCP_BUDDY_ADD=		40;
        public const int AOCP_BUDDY_REMOVE=		41;
        public const int AOCP_ONLINE_SET=		42;
        public const int AOCP_PRIVGRP_INVITE=		50;
        public const int AOCP_PRIVGRP_KICK=		51;
        public const int AOCP_PRIVGRP_JOIN=		52;
        public const int AOCP_PRIVGRP_PART=		53;
        public const int AOCP_PRIVGRP_KICKALL=		54;
        public const int AOCP_PRIVGRP_CLIJOIN=		55;
        public const int AOCP_PRIVGRP_CLIPART=		56;
        public const int AOCP_PRIVGRP_MESSAGE=		57;
        public const int AOCP_PRIVGRP_REFUSE=		58;
        public const int AOCP_GROUP_ANNOUNCE=		60;
        public const int AOCP_GROUP_PART=		61;
        public const int AOCP_GROUP_DATA_SET=		64;
        public const int AOCP_GROUP_MESSAGE=		65;
        public const int AOCP_GROUP_CM_SET=		66;
        public const int AOCP_CLIENTMODE_GET=		70;
        public const int AOCP_CLIENTMODE_SET=		71;
        public const int AOCP_PING=			100;
        public const int AOCP_FORWARD=			110;
        public const int AOCP_CC=			120;
        public const int AOCP_ADM_MUX_INFO=		1100;

        public const int AOCP_GROUP_JOIN=		AOCP_GROUP_ANNOUNCE; /* compat */

        public const int AOC_GROUP_NOWRITE=		0x00000002;
        public const int AOC_GROUP_NOASIAN=		0x00000020;
        public const int AOC_GROUP_MUTE=		0x01010000;
        public const int AOC_GROUP_LOG=			0x02020000;

        public const int AOC_BUDDY_KNOWN=		0x01;
        public const int AOC_BUDDY_ONLINE=		0x02;

        public const int AOC_FLOOD_LIMIT=		7;
        public const int AOC_FLOOD_INC=			2;

        public const int AOC_PRIORITY_HIGH=		1000;
        public const int AOC_PRIORITY_MED=		 500;
        public const int AOC_PRIORITY_LOW=		 100;

        public const int AOEM_UNKNOWN=                0xFF;
        public const int AOEM_ORG_JOIN=               0x10;
        public const int AOEM_ORG_KICK=               0x11;
        public const int AOEM_ORG_LEAVE=              0x12;
        public const int AOEM_ORG_DISBAND=            0x13;
        public const int AOEM_ORG_FORM=               0x14;
        public const int AOEM_ORG_VOTE=               0x15;
        public const int AOEM_ORG_STRIKE=             0x16;
        public const int AOEM_NW_ATTACK=              0x20;
        public const int AOEM_NW_ABANDON=             0x21;
        public const int AOEM_NW_OPENING=             0x22;
        public const int AOEM_NW_TOWER_ATT_ORG=       0x23;
        public const int AOEM_NW_TOWER_ATT=           0x24;
        public const int AOEM_NW_TOWER=               0x25;
        public const int AOEM_AI_CLOAK=               0x30;
        public const int AOEM_AI_RADAR=               0x31;
        public const int AOEM_AI_ATTACK=              0x32;
        public const int AOEM_AI_REMOVE_INIT=         0x33;
        public const int AOEM_AI_REMOVE=              0x34;
        public const int AOEM_AI_HQ_REMOVE_INIT=      0x35;
        public const int AOEM_AI_HQ_REMOVE=           0x36;


        string state = null;
        //object debug;
        Dictionary<string, string> chars = null;
        //object char_;
        Dictionary<string, string> buddies = new Dictionary<string, string>();
        Dictionary<string, int> gid = new Dictionary<string, int>();
        Dictionary<string, int> grp = new Dictionary<string, int>();
        Dictionary<string, int> id = new Dictionary<string, int>();
        Dictionary<string, int> id_dict = new Dictionary<string, int>();
	    Socket theSocket = null;
        //object callback;
         object cbargs = null;
        DateTime last_ping = DateTime.MinValue;
        object last_packet = null;
        private int login_num;
        private string username;        
        public static int serverPort = 7002;

        internal static byte[] substr(byte[] data, int p)
        {
            if (p < 0) throw new Exception("arg2(int) cannot be less than 0");
            if (p == 0) return new byte[0];
            if (p > data.Length) throw new Exception("arg2(int) cannot be more than data.length");

            byte[] suba = new byte[p];
            for(int idx = 0; idx < p; idx++)
            {
                suba[idx] = data[idx];
            }
            return suba;
        }

        #region "AOChatPacket"


        public static string substr(string str, int i1, int i2)
        {
            return str.Substring(i1, i2);
        }
        public static byte[] substr(byte[] ba_data, int s, int len)
        {
            if (ba_data == null) return null;
            byte[] ba_sub = new byte[len];
            if (s + len > ba_data.Length)
                throw new Exception("substr error: cannot go past end of string (arg2+arg3 > array length)");

            int cCount = 0;
            for (int didx = s; cCount < len; didx++)
            {
                ba_sub[cCount] = ba_data[didx];
                cCount++;
            }
            return ba_sub;
        }


        public static Random rnd;
        public static string get_random_hex_key(int bits)
        {
            rnd = new Random();
            string str = "";
            do
            {
                int i_rnd = rnd.Next(0, 0xff);
                str = str + string.Format("{0:x2}", i_rnd).ToLower();
                bits -= 8;
            } while (bits > 0);
            return str;
        }


        public static int strlen(string str)
        {
            return str.Length;
        }
        const int rnd_hex_key_bits = 256;
        ///    
        ///    This is 'half' Diffie-Hellman key exchange.
        ///    'Half' as in we already have the server's key (dhY)
        ///    dhN is a prime and dhG is generator for it.
        ///    http://en.wikipedia.org/wiki/Diffie-Hellman_key_exchange
        ///    
        public static byte[] generate_login_key(string servkey, string username, string password)
        {
            BigInteger dhY = phpMath.bighexdec("9c32cc23d559ca90fc31be72df817d0e124769e809f936bc14360ff4bed758f260a0d596584eacbbc2b88bdd410416163e11dbf62173393fbc0c6fefb2d855f1a03dec8e9f105bbad91b3437d8eb73fe2f44159597aa4053cf788d2f9d7012fb8d7c4ce3876f7d6cd5d0c31754f4cd96166708641958de54a6def5657b9f2e92");
            BigInteger dhN = phpMath.bighexdec("eca2e8c85d863dcdc26a429a71a9815ad052f6139669dd659f98ae159d313d13c6bf2838e10a69b6478b64a24bd054ba8248e8fa778703b418408249440b2c1edd28853e240d8a7e49540b76d120d3b1ad2878b1b99490eb4a2a5e84caa8a91cecbdb1aa7c816e8be343246f80c637abc653b893fd91686cf8d32d6cfe5f2a6f");
            BigInteger dhG = 5;//"0x5";

            string rnd_hex_key0 = get_random_hex_key();
            Console.WriteLine("Random hex key #0 ={0}  ...this key's length ={1}", rnd_hex_key0, rnd_hex_key0.Length);
            BigInteger bigRndKeyVal0 = phpMath.bighexdec(rnd_hex_key0);
            Console.WriteLine("Here is the value of that number in decimal: " + bigRndKeyVal0.ToString(10));

            BigInteger bigResult0 = phpMath.powmod(dhG, bigRndKeyVal0, dhN);
            string sBigHexResult = bigResult0.ToHexString().ToLower();

            //FOUND ERROR WHERE RESULT WAS A DECIMAL STRING NOT A HEXADCIMAL STRING (3:20PM THUR NOV, 28, 2010)
            byte[] dhX_Bytes = ASCIIEncoding.ASCII.GetBytes(sBigHexResult);
            Console.WriteLine("Result of powmod(...) returned the following hex bytes {0}", dhX_Bytes);




            string rnd_hex_key = get_random_hex_key(64);
            Console.WriteLine("Random 64 bit key of length {1} is {0}", rnd_hex_key, rnd_hex_key.Length);

            //str = sprintf("%s|%s|%s", username, servkey, password);
            string str = username + "|" + servkey + "|" + password;
            
            int length = 8 + 4 + str.Length; /* prefix, int, ... */
            int pad_len = (8 - length % 8) % 8;
            string pad = str_repeat(" ", pad_len);

            byte[] strlen = packN(str.Length);

            //string[] prefix = AOChat.packH16(rnd_hex_key);
            //string prefix = rnd_hex_key;//PackH16(rnd_hex_key);

            byte[] plain = Append(rnd_hex_key, strlen, str, pad);
            Console.WriteLine("Plain text of length={1} With value={0}", plain.ToString(), plain.Length);


            //FOUND ANOTHER SIMILAR ERROR WHERE RESULT WAS A DECIMAL STRING NOT A HEXADCIMAL STRING (3:20PM THUR NOV, 28, 2010)
            //also the BigInteger class returns upper case letters ABCDEF where as the other functions are using lowercase.
            string bigResult1 = phpMath.powmod(dhY, bigRndKeyVal0, dhN).ToHexString().ToLower();
            Console.WriteLine("Result of second powmod(...) operation returned Hex {0}", bigResult1);
            if (bigResult1.Length < 32)
            {
                bigResult1 = str_repeat("0", 32 - bigResult1.Length) + bigResult1;
            }
            else if (bigResult1.Length > 32)
            {
                bigResult1 = substr(bigResult1, 0, 32);
            }
            else if (bigResult1.Length == 32)
            {
                //bigResult1 = bigResult1
            }
            byte[] crypted = aochat_crypt(bigResult1, plain);
            Console.WriteLine("Final crypted bytes=", crypted.ToString());

            byte[] dash = StrToAscii("-") ;
            byte[] bFinal = Append(dhX_Bytes, dash, crypted);

            Console.WriteLine("Packet of length={1} is:{0}", bFinal.ToString(), bFinal.Length);

            int finalLen = bFinal.Length;
            return bFinal;
        }

        private static byte[] Append(string prefix, byte[] strlen, string str, string pad)
        {
            string appended = prefix + ASCIIEncoding.ASCII.GetString(strlen) + str + pad;
            return ASCIIEncoding.ASCII.GetBytes(appended);
        }


        private static string PackH16(string rnd_hex_key)
        {
            BigInteger[] parts = Partitition(rnd_hex_key, 16);


            //For a, A, h, H the repeat count specifies how many characters of one data argument are taken
            string prefix = packH_(parts);
            return prefix;
        }

        public static string get_random_hex_key()
        {
            const int rnd_hex_key_bits = 256;
            return get_random_hex_key(rnd_hex_key_bits);
        }

        //For a, A, h, H the repeat count specifies how many characters of one data argument are taken
        private static string packH_(BigInteger[] parts)
        {
            StringBuilder sb = new StringBuilder();
            foreach (BigInteger i in parts)
            {
                sb.Append(i.ToHexString().ToLower());
            }
            return sb.ToString();
        }


        /// <summary>
        /// 
        /// </summary>
        /// Make sure that 
        /// <param name="rnd_hex_key"></param>
        /// <returns></returns>
        public static BigInteger[] Partitition(string rnd_hex_key, int digits_per_pack)
        {   //1 nibble = 1 hex digit... because 2 hex digits = 1 byte and 1/2byte = 1 nibble
            //ex: rnd_hex_key = 9876543210abcde0 -> 98  76  54  32  10  ab  cd  e0 
            //                                       |   |   |   |   |   |   |   |
            //                                       
            //http://perldoc.perl.org/functions/pack.html Perl documentatio for pack compatible with PHP pack, I guess
            /*
             * The h and H formats pack a string that many nybbles (4-bit groups, representable as hexadecimal digits, "0".."9" "a".."f" ) long.

        For each such format, pack() generates 4 bits of the result. 
              With non-alphabetical characters, the result is based on the 4 least-significant bits of the input character, i.e., on ord($char)%16. 
              In particular, characters "0" and "1" generate nybbles 0 and 1, as do bytes "\0" and "\1" . 
             * For characters "a".."f" and "A".."F" , the result is compatible with the usual hexadecimal digits, so that "a" and "A" both generate the nybble 0xa==10 . Do not use any characters but these with this format.

        Starting from the beginning of the template to pack(), each pair of characters is converted to 1 character of output. 
             * With format h , the first character of the pair determines the least-significant nybble of the output character; 
             * with format H , it determines the most-significant nybble.

            */

            if (rnd_hex_key.Length % digits_per_pack != 0)
            {
                throw new Exception("Function Partitition does not support this number of characters in this version. Must be a multiple of " + digits_per_pack);
            }

            //char[] key_chars = rnd_hex_key.ToCharArray();

            //string s = "";
            //List<BigInteger> l = new List<BigInteger>();
            BigInteger[] ba = new BigInteger[rnd_hex_key.Length / digits_per_pack];
            int big_idx = 0;
            for (int idx = 0; idx < rnd_hex_key.Length; idx += digits_per_pack)
            {
                //    char hn = key_chars[idx]; //first character of pair determies most signification nybble 
                //    char ln = key_chars[idx + 1]; //least significant nibble
                //    uint decval_hn = phpMath.hexdec(hn);
                //    uint decval_ln = phpMath.hexdec(ln);
                string shex = rnd_hex_key.Substring(idx, digits_per_pack);
                BigInteger b = new BigInteger(shex, 16); //value represented
                ba[big_idx] = b;
                big_idx++;
                //    //byte val = (byte)(decval_hn * 16 + decval_ln);
                //    //string s = hn.ToString() + ln.ToString();
                //    char theChar = System.Text.ASCIIEncoding.ASCII.GetChars(new byte[] { val })[0];
                //    s = s + theChar;
            }
            return ba;
            
        }

        private static string Reverse(string s)
        {
            char[] key_chars = s.ToCharArray();
            Array.Reverse(key_chars);
            string srev = new string(key_chars);
            return srev;
        }


        //public static byte[] Append(byte[] ba1, byte[] ba2, byte[] ba3)
        //{
        //    return Append(Append(ba1, ba2), ba3);
        //}
        public static string Append(string[] prefix, byte[] strlen, string str, string pad)
        {
            return flatten(prefix) + Encoding.ASCII.GetString(strlen) + str + pad;
        }

        public static string flatten(string[] prefix)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string s in prefix)
            {
                sb.Append(s);
            }
            return sb.ToString();
        }

        public static byte[] Append(byte[] a, byte[] b, byte[] c)
        {
            string sAscii = ASCIIEncoding.ASCII.GetString(a) + ASCIIEncoding.ASCII.GetString(b) + ASCIIEncoding.ASCII.GetString(c);
            return ASCIIEncoding.ASCII.GetBytes(sAscii);
        }

        public static string Append(byte[] prefix, byte[] strlen, string str, string pad)
        {
            string ba_prefix = Encoding.ASCII.GetString(prefix);
            string ba_srlen = Encoding.ASCII.GetString(strlen);

            return ba_prefix + ba_srlen + str + pad;
        }

        public static string str_repeat(string p, int count)
        {
            StringBuilder sb = new StringBuilder("");
            for (int i = 1; i <= count; i++) sb.Append(p);
            return sb.ToString();
        }

        #region "ReduceTo32Bit function - PHP"

        //public static int ReduceTo32Bit(long value)
        //{

        //        //if($this -> big_cmp($value, 0) == -1)
        //        //{
        //        //    $value = $this -> NegativeToUnsigned($value);
        //        //}
        //        if (phpMath.big_cmp(value, 0) == -1)
        //        {
        //            value = (long) NegativeToUnsigned(value);
        //        }
        //        /////////////////////////////

        //        uint bit  = 0x80000000;
        //        Stack<uint> bits = new Stack<uint>();


        //        // Find the largest bit contained in $value above 32-bits
        //        while(value >= bit)
        //        {
        //            bit    = bit * 2;
        //            bits.Push(bit);
        //        }


        //        // Subtract out bits above 32 from $value
        //        //while(NULL != ($bit = array_pop($bits)))
        //        while(bits.Count > 0)
        //        {
        //            bit = bits.Pop();
        //            if (phpMath.big_cmp(value, bit) >= 0)
        //            {
        //                value = (int)value - bit;
        //            }
        //        }

        //        // Make negative if sign-bit is set in 32-bit value
        //        if (phpMath.big_cmp(value, 0x80000000) != -1)
        //        {
        //            value  = value - 0x80000000;
        //            value -= 0x80000000;
        //        }
        //        return (int)value;
        //}

        #endregion "ReduceTo32Bit function - PHP"

        /*
* This function returns the binary equivalent postive integer to a given negative
* integer of arbitrary length. This would be the same as taking a signed negative
* number and treating it as if it were unsigned. To see a simple example of this
* on Windows, open the Windows Calculator, punch in a negative number, select the
* hex display, and then switch back to the decimal display.
*/
        public static long NegativeToUnsigned(long value)
        {
            //if(bcmath.big_cmp(value, 0) != -1)
            if (value < 0)
                return value;

            value = value * -1;
            uint higherValue = 0xFFFFFFFF;

            // We don't know how many bytes the integer might be, so
            // start with one byte and then grow it byte by byte until
            // our negative number fits inside it. This will make the resulting
            // positive number fit in the same number of bytes.
            while (value > higherValue)
            {
                higherValue = higherValue * 0x100 + 0xFF;
            }

            value = (int)(higherValue - value + 1);
            return value;
        }

        // On linux systems, unpack("H*", pack("L*", <value>)) returns differently than on Windows.
        // This can be used instead of unpack/pack to get the value we need.
        //function SafeDecHexReverseEndian(value)
        /// <summary>
        /// Takes an int value converts it to hex, splits it 4 packs of 2 chars each, and contactenates the packs in reverse order (converting any unicode string to ascii, if possible)
        /// Otherwise it throws an exception.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static byte[] SafeDecHexReverseEndian(int value)
        {
            byte[] result = new byte[0];
            string hex = dechex(value);
            int len = hex == null ? 0 : hex.Length;

            while (len < 8)
            {
                hex = "0" + hex;
                len++;
            }

            string[] sa_bytes = str_split(hex, 2);

            for (int i = 3; i >= 0; i--)
            {
                string byteString = (string)sa_bytes[i];
                byte[] asciiString = StrToAscii(byteString);
                result = Append(result, asciiString);
            }

            return result;
        }

        public static string[] str_split(string hex, int partitionSize)
        {
            int mod = hex.Length % partitionSize;
            int hf_len = (hex.Length / 2);
            int el_count = hf_len + mod;
            string[] split = new string[el_count];
            for (int k = 0; k < (int)hex.Length / 2; k++)
            {
                split[k] = hex.Substring(2 * k, partitionSize);
            }
            return split;
        }

        public static string dechex(int p)
        {
            //should be inverse function of hexdec
            return p.ToString("x");
        }


        //function aochat_crypt(key, str)
        public static byte[] aochat_crypt(string key, byte[] stringData)
        {
            if (key.Length != 32)
            {
                throw new Exception("Key length must be 32");
            }

            int len = stringData.Length;
            if (len % 8 != 0)
            {
                //return null;
                throw new Exception("String to encrypt's length not multiple of eight.");
            }

            //$keyarr  = unpack("L*", pack("H*", $key));
            //$dataarr = unpack("L*", $str);

            //pack referenceL	unsigned long (always 32 bit, machine byte order) //H : Hex string, high nibble first
            //L : unsigned long (always 32 bit, machine byte order)

            //commented out because the key is already a hex string
            //char[] theChars = AOChat.packHStar(key);

            //16 to the power of 8 = 4 294 967 296
            //2 to the power of 32 = 4 294 967 296
            //therefore 8 hex digits go into every UInt32 pack
            UInt32[] keyarr = unpackLStarFromHexStar(key);



            //256 = char.MaxValue
            //256 to the power of x = 4 294 967 296 = 
            //well 256^4 = 4 294 967 296 so x= 4
            //2 to the power of 32 = 4 294 967 296 = UInt32.MaxValue
            //therefore 4 char digits go into every UInt32 pack
            byte[] bytes = stringData;

            //now i need to pack the bytes into a UInt32[]
            if (bytes.Length % 4 != 0)
                throw new Exception("The input bytes must be divisible by 4 (since there are 4 bytes of the Uint32 structure)");

            UInt32[] dataArr = new UInt32[bytes.Length / 4];//unpackLStar(bytes);
            int uidx = 0;
            for (int i = 0; i < bytes.Length; i += 4)
            {
                UInt32 u = BitConverter.ToUInt32(bytes, i);
                dataArr[uidx] = u;
                uidx++;
            }

            int[] now = new int[] { 0, 0 };//array(0, 0);
            int[] prev = new int[] { 0, 0 };//array(0, 0);
            byte[] ret = new byte[0];

            for (int i = 0; i < dataArr.Length; i += 2)//make array indexing start at 0 not 1
            {
                //I THINK THEY ARE LONGS
                //now[0] = (int)ReduceTo32Bit((long)dataarr[i]) ^ (int) ReduceTo32Bit(prev[0]);
                //now[1] = (int)ReduceTo32Bit((long)dataarr[i+1]) ^ (int) ReduceTo32Bit(prev[1]);
                now[0] = (int)(dataArr[i] ^ prev[0]);
                now[1] = (int)(dataArr[i + 1] ^ prev[1]);
                prev = aocrypt_permute(now, (UInt32[])keyarr);

                //ret .= SafeDecHexReverseEndian(prev[0]);
                byte[] hexSomeEndian0 = SafeDecHexReverseEndian(prev[0]);
                byte[] hexSomeEndian1 = SafeDecHexReverseEndian(prev[1]);
                ret = AOChat.Append(ret, hexSomeEndian0);
                ret = AOChat.Append(ret, hexSomeEndian1);

            }
            return ret;
        }
        //Please note that these String functions does not handle multibyte encodings such as UTF-8. Use the functions found in Multibyte String for these encodings. 
        //0xFFFF = 65 535 = char.MaxValue //not 256 (char is multi-byte (two bytes))
        //256 to the power of x = 4 294 967 296 = 
        //well 256^4 = 4 294 967 296 so x= 4
        //2 to the power of 32 = 4 294 967 296 = UInt32.MaxValue
        //therefore 4 char digits go into every UInt32 pack
        public static UInt32[] unpackLStar(byte[] stringData)
        {
            //take 4 bytes and convert to a uint32
            //well since pack L means pack as unsigned long (always 32 bit, machine byte order) 
            //unpack L would take a string hl + hl + hl + hl where hl means an unsigned long consisting of high and low nibbles
            throw new NotImplementedException();
        }

        public static UInt32[] packLStar(byte[] stringData)
        {
            throw new Exception("Just for reference. Not yet tested this implementation at all.");
            const int bytes_per_uint32 = 4;

            for (int idx = 0; idx < stringData.Length; idx += 4)
            {
                byte[] b = new byte[bytes_per_uint32];
                b[0] = stringData[idx]; //high nibble
                b[1] = stringData[idx + 1];
                b[2] = stringData[idx + 2];//low nibble
                b[3] = stringData[idx + 3];

                //b= hl
            }
        }        /// <summary>
        ///see http://perldoc.perl.org/functions/pack.html
        ///16 to the power of x = 4 294 967 296
        ///2 to the power of 32 = 4 294 967 296
        /// x = 8, therefore 8 hex digits go into every UInt32 pack
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public static UInt32[] unpackLStarFromHexStar(string p)
        {
            //see http://msdn.microsoft.com/en-us/library/system.uint32.maxvalue.aspx
            //how many hexadecimal digits are needed to make the max value of a uint32 ("The value of this constant is 4,294,967,295; that is, hexadecimal 0xFFFFFFFF.)" ?
            int sz = 8; //units: in hexadecimal digits per group
            int md = p.Length % sz;
            //Array.Reverse(ba);
            int pack_len = p.Length / sz;
            int j = 0;
            string part = null;
            UInt32[] unpacked = new UInt32[pack_len + ((md != 0) ? 1 : 0)];
            int idx;
            for (idx = 0; idx < pack_len; idx++)
            {
                j = sz * idx;
                part = p.Substring(j, sz);

                //new way
                const int base_ = 16;


                BigInteger b = new BigInteger(part, base_);
                unpacked[idx] = BigInteger.ToUInt32(b);

            }
            if (md != 0)
            {

                j = sz * idx;
                part = p.Substring(j, md);
                //this string is i need to refine or check my wording but is the most significant part of the hex string
                BigInteger b2 = phpMath.hexdec(p + str_repeat("0", sz - md));//be sure to pad otherwise it is wrong..
                unpacked[idx] = BigInteger.ToUInt32(b2);

            }
            return unpacked;
        }


        public static int[] toIntArray(long[] y)
        {
            if (y == null) return null;
            int[] ia = new int[y.Length];
            for (int i = 0; i < y.Length; i++)
            {
                ia[i] = (int)y[i];
            }
            return ia;
        }
        ///function aocrypt_permute(x, y)
        public static int[] aocrypt_permute(int[] x, UInt32[] y)
        {
            int a = x[0];
            int b = x[1];
            int c = 0;
            uint d = (uint)0x9e3779b9; //=2654435769 decimal, fits in uint but not int.

            for (int i = 32; i-- > 0; )
            {
                c = (int)(c + d);
                a += (int)((int)(((int)b << 4 & -16) + y[0]) ^ (int)(b + c)) ^ (int)(((int)b >> 5 & 134217727) + y[1]);
                b += (int)((int)(((int)a << 4 & -16) + y[2]) ^ (int)(a + c)) ^ (int)(((int)a >> 5 & 134217727) + y[3]);

            }
            return new int[] { a, b };
        }


        #region "AOChatPacket PHP OLD"

        ///* The AOChatPacket class - turning packets into binary blobs and binary
        //* blobs into packets
        //*/

        ///*
        //* I - 32 bit integer: uint32_t
        //* S - 8 bit string array: uint16_t length, char str[length]
        //* G - 40 bit binary data: unsigned char data[5]
        //* i - integer array: uint16_t count, uint32_t[count]
        //* s - string array: uint16_t count, aochat_str_t[count]
        //*
        //* D - 'data', we have relabeled all 'D' type fields to 'S'
        //* M - mapping [see t.class in ao_nosign.jar] - unsupported
        //*
        //*/

        #endregion "AOChatPacket PHP"


        class AOChatPacket
        {
            public string dir = null;
            public int type = -1;
            public byte[] data = null;
            public object[] args = null;

            private static Dictionary<string, string> newdic(string name, string args)
            {
                return newdic("name", name, "args", args);
            }

            private static Dictionary<string, string> newdic(string key1, string value1, string key2, string value2)
            {
                Dictionary<string, string> d = new Dictionary<string, string>();
                d.Add(key1, value1);
                d.Add(key2, value2);
                return d;
            }
            static Dictionary<string, string> _GLOBALS = null;
            private string p;
            private int AOCP_PRIVGRP_JOIN;
            private int gid;
            private int AOCP_LOGIN_SELECT;
            private string p_2;
            private static Dictionary<string, string> GLOBALS(string p, string dir, int type)
            {
                if (_GLOBALS == null)
                    _GLOBALS = InitializeGLOBALS(p, dir, type);
                return _GLOBALS;
            }

            private static Dictionary<string, string> InitializeGLOBALS(string p, string dir, int type)
            {
                //GLOBALS["aochat-packetmap"] = array(
                if (p == "aochat-packetmap")
                {
                    //"in" => array(
                    if (dir == "in")
                    {
                        switch (type)
                        {

                            //AOCP_LOGIN_SEED	=> array("name"=>"Login Seed",			"args"=>"S"),
                            case AOChat.AOCP_LOGIN_SEED: return newdic("Login Seed", "S");
                            case AOChat.AOCP_LOGIN_OK: return newdic("Login Result OK", "");
                            case AOChat.AOCP_LOGIN_ERROR: return newdic("Login Result Error", "S");
                            case AOChat.AOCP_LOGIN_CHARLIST: return newdic("Login CharacterList", "isii");
                            case AOChat.AOCP_CLIENT_UNKNOWN: return newdic("Client Unknown", "I");
                            case AOChat.AOCP_CLIENT_NAME: return newdic("Client Name", "IBS");
                            case AOChat.AOCP_CLIENT_LOOKUP: return newdic("Lookup Result", "IS");
                            case AOChat.AOCP_MSG_PRIVATE: return newdic("Message Private", "ISS");
                            case AOChat.AOCP_MSG_VICINITY: return newdic("Message Vicinity", "ISS");
                            case AOChat.AOCP_MSG_VICINITYA: return newdic("Message Anon Vicinity", "SSS");
                            case AOChat.AOCP_MSG_SYSTEM: return newdic("Message System", "S");
                            case AOChat.AOCP_CHAT_NOTICE: return newdic("Chat Notice", "IIIS");
                            case AOChat.AOCP_BUDDY_ADD: return newdic("Buddy Added", "IBBIB");
                            case AOChat.AOCP_BUDDY_REMOVE: return newdic("Buddy Removed", "I");
                            case AOChat.AOCP_PRIVGRP_INVITE: return newdic("Privategroup Invited", "I");
                            case AOChat.AOCP_PRIVGRP_KICK: return newdic("Privategroup Kicked", "I");
                            case AOChat.AOCP_PRIVGRP_PART: return newdic("Privategroup Part", "I");
                            case AOChat.AOCP_PRIVGRP_CLIJOIN: return newdic("Privategroup Client Join", "II");
                            case AOChat.AOCP_PRIVGRP_CLIPART: return newdic("Privategroup Client Part", "II");
                            case AOChat.AOCP_PRIVGRP_MESSAGE: return newdic("Privategroup Message", "IISS");
                            case AOChat.AOCP_PRIVGRP_REFUSE: return newdic("Privategroup Refuse Invite", "II");
                            case AOChat.AOCP_GROUP_ANNOUNCE: return newdic("Group Announce", "GSIS");
                            case AOChat.AOCP_GROUP_PART: return newdic("Group Part", "G");
                            case AOChat.AOCP_GROUP_MESSAGE: return newdic("Group Message", "GISS");
                            case AOChat.AOCP_PING: return newdic("Pong", "S");
                            case AOChat.AOCP_FORWARD: return newdic("Forward", "IM");
                            case AOChat.AOCP_ADM_MUX_INFO: return newdic("Adm Mux Info", "iii");
                        }
                    }
                    else if (dir == "out") //"out" => array(
                    {
                        switch (type)
                        {
                            case AOChat.AOCP_LOGIN_REQUEST: return newdic("Login Response GetCharLst", "ISS");
                            case AOChat.AOCP_LOGIN_SELECT: return newdic("Login Select Character", "I");
                            case AOChat.AOCP_CLIENT_LOOKUP: return newdic("Name Lookup", "S");
                            case AOChat.AOCP_MSG_PRIVATE: return newdic("Message Private", "ISS");
                            case AOChat.AOCP_BUDDY_ADD: return newdic("Buddy Add", "I");
                            case AOChat.AOCP_BUDDY_REMOVE: return newdic("Buddy Remove", "I");
                            case AOChat.AOCP_ONLINE_SET: return newdic("Onlinestatus Set", "I");
                            case AOChat.AOCP_PRIVGRP_INVITE: return newdic("Privategroup Invite", "I");
                            case AOChat.AOCP_PRIVGRP_KICK: return newdic("Privategroup Kick", "I");
                            case AOChat.AOCP_PRIVGRP_JOIN: return newdic("Privategroup Join", "I");
                            case AOChat.AOCP_PRIVGRP_PART: return newdic("Privategroup Part", "I");
                            case AOChat.AOCP_PRIVGRP_KICKALL: return newdic("Privategroup Kickall", "");
                            case AOChat.AOCP_PRIVGRP_MESSAGE: return newdic("Privategroup Message", "ISS");
                            case AOChat.AOCP_GROUP_DATA_SET: return newdic("Group Data Set", "GIS");
                            case AOChat.AOCP_GROUP_MESSAGE: return newdic("Group Message", "GSS");
                            case AOChat.AOCP_GROUP_CM_SET: return newdic("Group Clientmode Set", "GIIII");
                            case AOChat.AOCP_CLIENTMODE_GET: return newdic("Clientmode Get", "IG");
                            case AOChat.AOCP_CLIENTMODE_SET: return newdic("Clientmode Set", "IIII");
                            case AOChat.AOCP_PING: return newdic("Ping", "S");
                            case AOChat.AOCP_CC: return newdic("CC", "ISS");
                            default: throw new Exception("Setting not found in globals..");
                        }
                    }
                }
                return null;
            }

            public AOChatPacket(string dir, int type, object data)
            {
                this.args = new object[0];
                this.type = type;
                this.dir = dir;
                Dictionary<string, string> pmap = GLOBALS("aochat-packetmap", dir, type);

                if (pmap == null)
                {
                    AOChat.echo("Unsupported packet type (" + dir + ", " + type + ")\n");
                    return;
                }
                if (dir == "in")
                {
                    byte[] ba_data = ((byte[])data);
                    if (ba_data == null || ba_data.Length == 0)
                    {
                        echo("Incorrect argument for incoming packet, expecting a byte[].\n");
                        return;
                    }

                    int i = 0;
                    while (i < pmap["args"].Length)
                    {
                        char sa = pmap["args"][i];
                        object temparray;
                        object res; //can be uint
                        uint len;

                        switch (sa)
                        {

                            //                case "I" :
                            //                    $temparray = unpack("N", $data);
                            //                    $res  = array_pop($temparray);
                            //                    $data = substr($data, 4);
                            //                    break;
                            case 'I':
                                //uint[] uarray = (uint[])temparray;

                                ushort[] temparray_uinta = (ushort[])AOChat.unpackn(ba_data);
                                //$res  = array_pop($uarray);
                                temparray = temparray_uinta;
                                res = array_pop<ushort>(ref temparray_uinta);
                                temparray = temparray_uinta;
                                data = substr(ba_data, 4);
                                break;

                            //                case "B" :
                            //                    $temparray = unpack("C", $data);
                            //                    $res  = array_pop($temparray);
                            //                    $data = substr($data, 1);
                            //                    break;
                            case 'B':
                                char[] carray = unpackC(ba_data); // C : unsigned char
                                char cRes = array_pop<char>(ref carray);
                                data = ba_data[0];
                                temparray = carray;
                                res = cRes;
                                break;

                            //                case "S" :
                            //                    $temparray = unpack("n", $data);
                            //                    $len  = array_pop($temparray);
                            //                    $res  = substr($data, 2, $len);
                            //                    $data = substr($data, 2 + $len);
                            //                    break;
                            case 'S':
                                ushort[] uarray = unpackn(ba_data);
                                len = array_pop<ushort>(ref uarray);
                                res = substr(ba_data, 2, (int)len);
                                data = substr(ba_data, 2 + (int)len);
                                temparray = uarray;
                                break;

                            //                case "G" :
                            //                    $res  = substr($data, 0, 5);
                            //                    $data = substr($data, 5);
                            //                    break;

                            case 'G':
                                res = substr(ba_data, 0, 5);
                                data = substr(ba_data, 5);
                                break;

                            case 'i':
                                uarray = unpackn(ba_data);
                                len = array_pop<ushort>(ref uarray);
                                res = unpackn(substr(ba_data, 2));
                                data = substr(ba_data, 2 + 4 * (int)len);
                                temparray = uarray;
                                break;

                            case 's':
                                uarray = unpackn(ba_data);
                                len = array_pop<ushort>(ref uarray);
                                data = substr(ba_data, 2);
                                res = new byte[0];

                                while (len-- > 0)
                                {
                                    uarray = unpackn(ba_data);
                                    ushort slen = array_pop<ushort>(ref uarray);
                                    res = substr(ba_data, 2, (int)slen);
                                    data = substr(ba_data, (int)(2 + slen));
                                    temparray = uarray;
                                }
                                break;

                            default:
                                echo("Unknown argument type! (" + sa + ")\n");
                                i += 2;
                                continue;
                        }

                        //convert each byte to an object forming an object[]
                        byte[] res_bytes = (byte[])res;
                        object[] newBytes = new object[res_bytes.Length];
                        int idx=0;
                        foreach (byte resB in res_bytes)
                        {
                            newBytes[idx++] = resB as object;
                        }

                        this.args = newBytes;
                        i++;
                    }
                }
                //dir = out, so you know
                else
                {
                    if (data is object[])
                    {
                        args = ((object[])data);
                    }
                    else
                    {
                        //args = data;
                        args = new object[1];
                        args[0] = data;
                    }

                    data = null;

                    for (int i = 0; i < pmap["args"].Length; i++)
                    {
                        char sa = pmap["args"][i];
                        object it = array_shift<object>(ref args);

                        if (it == null)
                        {
                            echo("Missing argument for packet.\n");
                            break;
                        }

                        switch (sa)
                        {

                            case 'I':
                                //data .= pack("N", it);
                                byte[] pack = packN((int)it);
                                data = Append((byte[])data, pack);
                                break;

                            //            case "i" :
                            //                $data .= pack("n", $it);
                            //                break;


                            case 'i':
                                byte[] pack2 = packN((int)it);
                                data = Append((byte[])data, pack2);
                                break;

                            //            case "S" :
                            //                $data .= pack("n", strlen($it)) . $it;
                            //                break;

                            case 'S':
                                //byte[] it_byte_array = ASCIIEncoding.ASCII.GetBytes(it);
                                int slen = ((byte[])it).Length;
                                byte[] ba = packn(slen);//ASCIIEncoding.ASCII.GetBytes(it);
                                //data = new object[] {ba, it};
                                if (!(it is byte[]))
                                    throw new Exception("AOChatPacket constructor expecting string. Line 404.");
                                byte[] s_bytes = (byte[])it;//StrToAscii((string)it);
                                data = Append((byte[])data, ba, s_bytes);
                                break;
                            //            case "G" :
                            //                $data .= $it;
                            //                break;

                            case 'G':
                                data = Append((byte[])data, (byte[])it);
                                break;
                            //            case "s" :
                            //                $data .= pack("n", sizeof($it));
                            //                foreach($it as $it_elem)
                            //                $data .= pack("n", strlen($it_elem)) . $it_elem;
                            //                break;

                            case 's':
                                int itlen = ((byte[])it).Length;

                                data = Append((byte[])data, packn(itlen));

                                foreach (byte it_elem in (byte[])it)
                                    data = Append((byte[])data, packn(itlen), new byte[] { it_elem });

                                break;
                            //            default :
                            //                echo "Unknown argument type! (" . $sa . ")\n";
                            //                continue(2);
                            //        }
                            default:
                                echo("Unknown argument type! (" + sa + ")\n");
                                i += 2;
                                continue;
                        }
                    }
                    this.data = (byte[])data;
                }
                return;
            }

            public AOChatPacket(string p, int AOCP_PRIVGRP_JOIN, int gid)
            {
                // TODO: Complete member initialization
                this.p = p;
                this.AOCP_PRIVGRP_JOIN = AOCP_PRIVGRP_JOIN;
                this.gid = gid;
            }

            public AOChatPacket(string p, int AOCP_LOGIN_SELECT, string p_2)
            {
                // TODO: Complete member initialization
                this.p = p;
                this.AOCP_LOGIN_SELECT = AOCP_LOGIN_SELECT;
                this.p_2 = p_2;
            }

            /// <summary>
            /// array_shift() shifts the first value of the array off and returns it, shortening the array by one element and moving everything down. All numerical array keys will be modified to start counting from zero while literal keys won't be touched. 
            /// </summary>
            /// <param name="args"></param>
            /// <returns></returns>
            private T array_shift<T>(ref T[] args)
            {
                if (args == null)
                    return default(T);
                if (args.Length == 0)
                    return default(T);


                T shiftOff = args[0];
                List<T> newlist = new List<T>();

                for (int i = 1; i < args.Length; i++)
                {
                    newlist.Add(args[i]);
                }
                //replace the old with the new
                args = newlist.ToArray();

                return shiftOff;
            }

            //super slow way to do an array pop for something the array structure was never intended for
            public static T array_pop<T>(ref T[] temparray)
            {
                T[] obja = new T[temparray.Length];
                temparray.CopyTo(obja, temparray.Length);
                Stack<T> s = new Stack<T>(obja);
                temparray = s.ToArray();
                return (T)s.Pop();
            }

        } //end of class

        #endregion

    }
}
